﻿#include <windowsx.h>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
#include <portaudio.h>
}

#define MAX_AUDIO_FRAME_SIZE 19200
#include <iostream>
#include <windows.h>
#include <string>
#include "Queue.h"
#include <atomic>
#include <d3d9.h>
#pragma comment(lib, "d3d9.lib")
#include <wrl.h>
using Microsoft::WRL::ComPtr;

static std::atomic<bool> is_quit(false);

struct Color_RGB
{
    uint8_t r;
    uint8_t g;
    uint8_t b;
};

std::string gbk_to_utf8(const std::string& gbkStr)
{
    // 获取缓冲区大小
    int len = MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), -1, NULL, 0);
    wchar_t* wstr = new wchar_t[len];
    // GBK 转 Unicode
    MultiByteToWideChar(CP_ACP, 0, gbkStr.c_str(), -1, wstr, len);

    // 获取 UTF-8 缓冲区大小
    len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
    char* utf8Str = new char[len];
    // Unicode 转 UTF-8
    WideCharToMultiByte(CP_UTF8, 0, wstr, -1, utf8Str, len, NULL, NULL);

    std::string result(utf8Str);
    delete[] wstr;
    delete[] utf8Str;
    return result;
}

BlockingQueue<AVFrame*> video_queue(10);
BlockingQueue<AVFrame*> audio_queue(10);
AVCodecContext* audio_decode_ctx;
AVCodecContext* video_decode_ctx;
static std::atomic<long> audio_pts(0);
AVFormatContext* in_fmt_ctx = nullptr;

void audio_play()
{
    auto out_sample_fmt = AV_SAMPLE_FMT_S16;
    SwrContext* au_convert_ctx = swr_alloc();
    AVChannelLayout out_ch_layout = AV_CHANNEL_LAYOUT_STEREO;

    swr_alloc_set_opts2(&au_convert_ctx,
                        &out_ch_layout, out_sample_fmt, audio_decode_ctx->sample_rate,
                        &audio_decode_ctx->ch_layout, audio_decode_ctx->sample_fmt,
                        audio_decode_ctx->sample_rate,
                        0, nullptr);
    swr_init(au_convert_ctx);


    uint8_t* out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);
    PaStream* stream = nullptr;
    Pa_OpenDefaultStream(&stream, 0, audio_decode_ctx->ch_layout.nb_channels,
                         paInt16, audio_decode_ctx->sample_rate,
                         2048, nullptr, nullptr);
    Pa_StartStream(stream);
    while (!is_quit)
    {
        AVFrame* frame = audio_queue.pop();
        if (frame == nullptr)
        {
            break;
        }
        swr_convert(au_convert_ctx, &out_buffer, MAX_AUDIO_FRAME_SIZE, frame->data, frame->nb_samples);
        Pa_WriteStream(stream, out_buffer, frame->nb_samples);
        audio_pts = frame->pts;
        av_frame_unref(frame);
        av_frame_free(&frame);
    }

    Pa_StopStream(stream);
    Pa_CloseStream(stream);
    std::cout << "audio thread quit\n";
}

HWND window;

void RenderHWFrame(HWND hwnd, AVFrame* frame)
{
    IDirect3DSurface9* surface = (IDirect3DSurface9*)frame->data[3];
    IDirect3DDevice9* device;
    surface->GetDevice(&device);

    static ComPtr<IDirect3DSwapChain9> mySwap;
    if (mySwap == nullptr)
    {
        D3DPRESENT_PARAMETERS params = {};
        params.Windowed = TRUE;
        params.hDeviceWindow = hwnd;
        params.BackBufferFormat = D3DFORMAT::D3DFMT_X8R8G8B8;
        params.BackBufferWidth = frame->width;
        params.BackBufferHeight = frame->height;
        params.SwapEffect = D3DSWAPEFFECT_DISCARD;
        params.BackBufferCount = 1;
        params.Flags = 0;
        device->CreateAdditionalSwapChain(&params, mySwap.GetAddressOf());
    }

    ComPtr<IDirect3DSurface9> backSurface;
    mySwap->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, backSurface.GetAddressOf());

    device->StretchRect(surface, NULL, backSurface.Get(), NULL, D3DTEXF_LINEAR);

    mySwap->Present(NULL, NULL, NULL, NULL, NULL);
}

void video_play()
{
    ComPtr<IDirect3D9> d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
    ComPtr<IDirect3DDevice9> d3d9Device;

    D3DPRESENT_PARAMETERS d3dParams = {};
    d3dParams.Windowed = TRUE;
    d3dParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dParams.BackBufferFormat = D3DFORMAT::D3DFMT_X8R8G8B8;
    d3dParams.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
    d3dParams.BackBufferWidth = video_decode_ctx->width;
    d3dParams.BackBufferHeight = video_decode_ctx->height;
    d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dParams,
                       d3d9Device.GetAddressOf());
    
    while (!is_quit)
    {
        AVFrame* frame = video_queue.pop();
        while (!is_quit)
        {
            if (frame==nullptr)
            {
                break;
            }
            if (frame->pts > audio_pts)
            {
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
                continue;
            }
            
            RenderHWFrame(window, frame);
            break;
        }

        av_frame_unref(frame);
        av_frame_free(&frame);
    }

    std::cout << "video thread quit\n";
}

void open_file()
{
    std::string src_name = u8"E:/mv/10_【乐正绫×中国航天】月梦千年(日冕版) Endless Dream Of The Moon.mp4";

    PaError pa_error = Pa_Initialize();
    if (pa_error != paNoError)
    {
        return;
    }


    avformat_open_input(&in_fmt_ctx, src_name.c_str(), nullptr, nullptr);
    avformat_find_stream_info(in_fmt_ctx, nullptr);
    int audio_index = -1;
    int video_index = -1;


    for (int i = 0; i < in_fmt_ctx->nb_streams; i++)
    {
        auto av_stream = in_fmt_ctx->streams[i];
        if (av_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            video_index = i;
            const AVCodec* codec = avcodec_find_decoder(av_stream->codecpar->codec_id);
            video_decode_ctx = avcodec_alloc_context3(codec);
            avcodec_parameters_to_context(video_decode_ctx, av_stream->codecpar);
            avcodec_open2(video_decode_ctx, codec, nullptr);
            AVBufferRef* hw_device_ctx = nullptr;
            av_hwdevice_ctx_create(&hw_device_ctx, AVHWDeviceType::AV_HWDEVICE_TYPE_DXVA2, NULL, NULL, NULL);
            video_decode_ctx->hw_device_ctx = hw_device_ctx;
        }
        else if (av_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            audio_index = i;
            const AVCodec* codec = avcodec_find_decoder(av_stream->codecpar->codec_id);
            audio_decode_ctx = avcodec_alloc_context3(codec);
            avcodec_parameters_to_context(audio_decode_ctx, av_stream->codecpar);
            avcodec_open2(audio_decode_ctx, codec, nullptr);
        }
    }


    AVPacket* packet = av_packet_alloc();
    AVFrame* frame = av_frame_alloc();

    std::thread audio_play_thread(audio_play);
    std::thread video_play_thread(video_play);


    while (av_read_frame(in_fmt_ctx, packet) >= 0)
    {
        if (packet->stream_index == audio_index)
        {
            if (avcodec_send_packet(audio_decode_ctx, packet) < 0)
            {
                break;
            }
            while (avcodec_receive_frame(audio_decode_ctx, frame) >= 0)
            {
                auto frame_clone = av_frame_clone(frame);
                audio_queue.push(frame_clone);
            }
        }
        if (packet->stream_index == video_index)
        {
            if (avcodec_send_packet(video_decode_ctx, packet) < 0)
            {
                break;
            }
            while (avcodec_receive_frame(video_decode_ctx, frame) >= 0)
            {
                auto frame_clone = av_frame_clone(frame);
                video_queue.push(frame_clone);
            }
        }
        if (is_quit)
        {
            break;
        }
        av_packet_unref(packet);
    }
    av_frame_free(&frame);
    av_packet_free(&packet);
    avformat_close_input(&in_fmt_ctx);
    avformat_free_context(in_fmt_ctx);

    std::cout << "file thread quit\n";
    audio_play_thread.join();
    video_play_thread.join();
}

int main(int argc, char* argv[])
{
    std::thread open_file_thread(open_file);
    SetProcessDPIAware();

    auto className = L"MyWindow";
    WNDCLASSW wndClass = {};
    wndClass.hInstance = NULL;
    wndClass.lpszClassName = className;
    wndClass.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT {
        switch (msg)
        {
        case WM_DESTROY:
            is_quit = true;
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
        }
    };

    RegisterClassW(&wndClass);
    window = CreateWindowW(className, L"播放器", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 1280, 720,
                           NULL, NULL, NULL, NULL);

    ShowWindow(window, SW_SHOW);

    MSG msg;
    while (GetMessageW(&msg, window, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    open_file_thread.join();

    return 0;
}
